package domain.message ;

import java.util.Date ;
import java.util.Vector ;

import presentation.UIDirectory ;
import presentation.UIPost ;
import presentation.UIThread ;
import util.Bug ;
import util.Log ;
import util.NotFoundException ;
import util.Subsystem ;
import domain.ForumData ;
import domain.searchEngine.IndexAgent ;
import domain.user.Member ;
import domain.user.Privilege ;
import domain.user.UnpermittedActionException ;
import domain.user.User ;

/**
 * a controller for all messages actions and manipulations.
 *
 * at the moment all its methods gets the user and the message location as domain objects.
 *
 * at the future will hold all methods that are needed by GUI to access messages and present forum pages: (as
 * getting content of directories and threads by id) and all administrator methods (as adding / deleting
 * directories, and deleting threads and posts)
 *
 */
public class MessageController
{

	/** reference to the forum object which holds the users and the messages in the forum system **/
	private final ForumData forum ;

	/** the index agent that should update index with any new message **/
	private IndexAgent indexAgent;

	private Directory statisticsDir ; // TODO
	private Thread statisticsThread ; // TODO

	/**
	 * constructor.
	 * @param forum the forum object which holds the users and the messages in the forum system
	 * @param indexA the index agent to maintain the search-data updated
	 * @post a MessageController object that holds the forum is created
	 */
	public MessageController(final ForumData forum, final IndexAgent indexA)
	{
		this.forum = forum;
		this.indexAgent = indexA;
	}

	/**
	 * add new subDirectory under an existing directory
	 * (the root is build when the forum is initialized)
	 * @param userId the user which ask to add this directory
	 * @param containerDirectoryId the id of the directory under it the new subDirectory will be added
	 * @param newDirName the name for the new subDirectory
	 * @return the new directory for presentation
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the container directory was not found
	 */
	public UIDirectory addDirectory(final long userId, final long containerDirectoryId, final String newDirName) throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " requests to add a directory named " +
			newDirName + " to directory " + containerDirectoryId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.ADD_DIRECTORY))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Directory dir = this.forum.getMessages().getDirectory(containerDirectoryId);
			final Directory newDir = this.forum.getMessages().createDirectory(newDirName) ;

			//add the new subDirectory to the directory:
			dir.addDirectory(newDir.getId());
			this.forum.getMessages().flush(dir); //update changes in directory to the persistent

			return newDir;
		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}


	/**
	 * create a new post in thread.
	 * @param threadId location of the new post
	 * @param userId the id of the user requested to perform this action
	 * @param msg the wanted message in the post
	 * @return the new post for presentation
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the thread or the user was not found
	 * @post messages has a new post with the wanted message, and the wanted thread holds its id.
	 */
	public UIPost addPost(final long threadId, final long userId, final String msg)
		throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).fine("user " + userId + " requests to add a post to thread " + threadId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.ADD_POST))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Thread thread = this.forum.getMessages().getThread(threadId);
			final Post newPost = this.forum.getMessages().createPost(msg, userId, thread.getId()) ;

			//add the post to the thread and to index:
			thread.addPost(newPost.getId());
			this.forum.getMessages().flush(thread); //update changes in thread to the persistent
			this.notifyIndexer(user, newPost) ;
			return newPost;

		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}
	
	/**
	 * create a new post in thread with faked date.
	 * @param threadId location of the new post
	 * @param userId the id of the user requested to perform this action
	 * @param msg the wanted message in the post
	 * @param time the faked date
	 * @return the new post for presentation
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the thread or the user was not found
	 * @post messages has a new post with the wanted message, and the wanted thread holds its id.
	 */
	public UIPost addPost(long threadId, long userId, String msg, Date time)
		throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).fine("user " + userId + " requests to add a post to thread " + threadId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.ADD_POST))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Thread thread = this.forum.getMessages().getThread(threadId);
			final Post newPost = this.forum.getMessages().createPost(msg, userId, thread.getId()) ;
			newPost.setDate(time); //fake the post's date
			this.forum.getMessages().flush(newPost);
			
			//add the post to the thread and to index:
			thread.addPost(newPost.getId());
			this.forum.getMessages().flush(thread); //update changes in thread to the persistent
			this.notifyIndexer(user, newPost) ;
			return newPost;

		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}		
	}

	/**
	 * create a new thread in a sub-forum, with a first post.
	 * @param dirId location of the new thread
	 * @param userId the member requested to perform this action
	 * @param subject the thread subject
	 * @param msg the wanted message for the first post in the new created thread
	 * @return the new thread for presentation
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the directory or the user was not found
	 * @post a new thread with the wanted subject was created in the the wanted thread, and its first post
	 * includes the given message.
	 */
	public UIThread addThread(final long dirId, final long userId, final String subject, final String msg)
		throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " requests to add a thread named " +
			subject + " to directory " + dirId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.ADD_THREAD))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Directory dir = this.forum.getMessages().getDirectory(dirId);
			final Thread newThread = this.forum.getMessages().createThread(subject) ;
			final Post newPost = this.forum.getMessages().createPost(msg, userId, newThread.getId()) ;
			this.notifyIndexer(user, newPost) ;

			//add the first post to the thread:
			newThread.addPost(newPost.getId());
			this.forum.getMessages().flush(newThread); //update changes in thread to the persistent

			//add the thread to the directory:
			dir.addThread(newThread.getId());
			this.forum.getMessages().flush(dir); //update changes in directory to the persistent

			return newThread;
		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}


	/**
	 * delete a post with all its content from a thread
	 * @param userId the user which ask to add this directory
	 * @param threadId the id of the thread under it the post should be deleted
	 * @param postId the id of the post to delete
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the container directory was not found
	 */
	public void deletePost(final long userId, final long postId, final long threadId) throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " requests to delete a post " +
			postId + " from thread " + threadId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.DELETE_POST))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Post post = this.forum.getMessages().getPost(postId);
			final Thread thread = this.forum.getMessages().getThread(threadId) ;

			//delete the post from the thread:
			thread.deletePost(postId);
			this.forum.getMessages().flush(thread); //update changes in thread to the persistent

			//delete the posts from index:
			final Member writer = this.forum.getUsers().getMember(post.getWriterId());
			this.indexAgent.removePostFromIndex(post, writer.getUsername());

			//delete post from messages repository:
			this.forum.getMessages().deletePost(postId);

		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}


	/**
	 * delete a thread with all its content from a directory
	 * @param userId the user which ask to add this directory
	 * @param dirId the id of the directory under it the thread should be deleted
	 * @param threadId the id of the thread to delete
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the container directory was not found
	 */
	public void deleteThread(final long userId, final long dirId, final long threadId) throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " requests to delete a thread " +
			threadId + " from directory " + dirId + ".") ;
		final User user = this.forum.getUsers().getUser(userId) ;

		// check permission to perform this action:
		if (user.getPrivileges().contains(Privilege.DELETE_THREAD))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Directory dir = this.forum.getMessages().getDirectory(dirId);
			final Thread thread = this.forum.getMessages().getThread(threadId) ;

			//delete the thread from the directory:
			dir.deleteThread(threadId);
			this.forum.getMessages().flush(dir); //update changes in directory to the persistent

			//delete all the posts of the thread from index:
			final Vector<Long> messagesIds = thread.getPosts();
			for(int i=0; i<messagesIds.size(); i++)
			{
				final long id = messagesIds.elementAt(i);
				final Post post = this.forum.getMessages().getPost(id);
				final Member writer = this.forum.getUsers().getMember(post.getWriterId());
				this.indexAgent.removePostFromIndex(post, writer.getUsername());
			}

			//delete thread from messages repository:
			this.forum.getMessages().deleteThread(threadId);

		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}


	/**
	 * edit the wanted post (only if member is the writer of the post, or moderator)
	 * @param postId the wanted post to edit
	 * @param userId the member requested to perform this action
	 * @param msg the new message to edit instead of the current message in the post
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the post or the user was not found
	 * @post the post holds the new message (msg) instead of the previous one.
	 */
	public void editPost(final long postId, final long userId, final String msg)
		throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " is requesting to edit post " + postId + ".") ;
		final User user = this.forum.getUsers().getUser(userId);
		final Post post = this.forum.getMessages().getPost(postId);

		//check permission to perform this action, and check if member is the writer of post,
		//or it is a moderator that can edit any post:
		if( (user.getPrivileges().contains(Privilege.EDIT_POST) & post.getWriterId() == user.getId()) |
			(user.getPrivileges().contains(Privilege.EDIT_ANY_POST)) )
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			final Member writer = this.forum.getUsers().getMember(post.getWriterId());
			this.indexAgent.removePostFromIndex(post, writer.getUsername());
			post.edit(msg);
			this.forum.getMessages().flush(post); //update changes in post to the persistent
			this.indexAgent.addNewPostToIndex(post, writer.getUsername());
		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}

	/**
	 * edit the wanted thread topic
	 * @param threadId the wanted thread to edit
	 * @param userId the member requested to perform this action
	 * @param topic the new topic to edit instead of the current topic in the thread
	 * @throws UnpermittedActionException if the user doesn't own the appropriate Privilege for performing
	 * this action
	 * @throws NotFoundException if the post or the user was not found
	 * @post the thread holds the new topic instead of the previous one.
	 */
	public void editThreadTopic(final long threadId, final long userId, final String topic)
		throws UnpermittedActionException, NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("user " + userId + " is requesting to edit topic of thread " + threadId + ".") ;
		final User user = this.forum.getUsers().getUser(userId);
		final Thread thread = this.forum.getMessages().getThread(threadId);

		//check permission to perform this action (can be done only by a moderator that can edit any post,
		//including the first one that has set the topic):
		if (user.getPrivileges().contains(Privilege.EDIT_ANY_POST))
		{
			Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
			thread.setTopic(topic);
			this.forum.getMessages().flush(thread); //update changes in thread to the persistent
		}
		else
		{
			Log.getLogger(Subsystem.SERVICES).info("unpermitted action.") ;
			throw new UnpermittedActionException() ;
		}
	}

	/**
	 * handle messages and indexAgent for exit
	 */
	public void exit()
	{
		//close message server:
		this.forum.getMessages().close();
	}

	/**
	 * @param id id of the directory to get.
	 * @return directory whose id is 'id'.
	 * @throws NotFoundException if no such directory exists.
	 * @pre directory with such id exists.
	 */
	public UIDirectory getDirectory(final long id) throws NotFoundException
	{
		return this.forum.getMessages().getDirectory(id) ;
	}

	/**
	 * get all threads's posts in a presentation form
	 * @param threadId the thread to retrieve its threads
	 * @return vector of all posts in a form of passing to the presentation layer
	 * @throws NotFoundException when the thread was not found
	 */
	public Vector<UIPost> getPosts(final long threadId) throws NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("viewing thread " + threadId + " posts.") ;
		final Thread thread = this.forum.getMessages().getThread(threadId);
		final Vector<Long> postsIds = thread.getPosts();
		final Vector<UIPost> posts = new Vector<UIPost>();
		for(int i=0; i<postsIds.size(); i++)
		{
			final long id = postsIds.elementAt(i);
			posts.add(this.forum.getMessages().getPost(id));
		}
		return posts;
	}

	/**
	 * @return the root directory in presentation form
	 */
	public UIDirectory getRootDirectory()
	{
		return this.forum.getRoot();
	}


	/**
	 * get all directory's subDirectories in a presentation
	 * @param dirId the directory to retrieve its subDirectories
	 * @return vector of all subDirectories in a form of passing to the presentation layer
	 * @throws NotFoundException when the directory was not found
	 */
	public Vector<UIDirectory> getSubDirectories(final long dirId) throws NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("viewing directory " + dirId + " subDirectories.") ;
		final Directory directory = this.forum.getMessages().getDirectory(dirId);
		final Vector<Long> childrenIds = directory.getChildren();
		final Vector<UIDirectory> subDirectories= new Vector<UIDirectory>();
		for(int i=0; i<childrenIds.size(); i++)
		{
			final long id = childrenIds.elementAt(i);
			subDirectories.add(this.forum.getMessages().getDirectory(id));
		}
		return subDirectories;
	}

	/**
	 * @param id id of the thread to get.
	 * @return thread whose id is 'id'.
	 * @throws NotFoundException if no such thread exists.
	 * @pre thread with such id exists.
	 */
	public UIThread getThread(final long id) throws NotFoundException
	{
		return this.forum.getMessages().getThread(id) ;
	}

	/**
	 * get all directory's threads in a presentation form
	 * @param dirId the directory to retrieve its threads
	 * @return vector of all threads in a form of passing to the presentation layer
	 * @throws NotFoundException when the directory was not found
	 */
	public Vector<UIThread> getThreads(final long dirId) throws NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("viewing directory " + dirId + " threads.") ;
		final Directory directory = this.forum.getMessages().getDirectory(dirId);
		final Vector<Long> threadsIds = directory.getThreads();
		final Vector<UIThread> threads = new Vector<UIThread>();
		for(int i=0; i<threadsIds.size(); i++)
		{
			final long id = threadsIds.elementAt(i);
			threads.add(this.forum.getMessages().getThread(id));
		}
		return threads;
	}

	/**
	 * method used for presentation. post time and date can be set ahead. TODO
	 * the post content will be arbitrary.
	 * @param username user which posted the post.
	 * @param time time of the posting.
	 */
	public void putPost(final String username, final Date time)
	{
		try
		{
			if (!this.statisticsTestDirExists())
			{
				this.statisticsDir = (Directory)this.addDirectory(1, this.forum.getRoot().getId(), "statistics") ;
				this.statisticsThread = (Thread)this.addThread(this.statisticsDir.getId(), 1, "statistics tests", "post here") ;
			}
			Member m = this.forum.getUsers().getMember(username) ;
			this.addPostAt(m, time) ;
		}
		catch (NotFoundException e)
		{
			throw new Bug() ;
		}
		catch (UnpermittedActionException e)
		{
			throw new Bug() ;
		}
	}

	/**
	 * view the wanted post
	 * @param postId the wanted post to view
	 * @return the post in a form of passing to the presentation layer
	 * for performing this action
	 * @throws NotFoundException if the post or the user was not found
	 * @post no change is made in the system.
	 **/
	public UIPost viewPost(final long postId) throws NotFoundException
	{
		Log.getLogger(Subsystem.SERVICES).info("viewing post " + postId + ".") ;
		final Post post = this.forum.getMessages().getPost(postId);
		return post;
	}

	// TODO
	private void addPostAt(final Member member, final Date time)
	{
		Log.getLogger(Subsystem.SERVICES).finest("permission granted.") ;
		final Post newPost = this.forum.getMessages().createPost("this message was created on " + time, member.getId(), this.statisticsThread.getId()) ;
		newPost.setDate(time) ;

		//add the post to the thread and to index:
		this.statisticsThread.addPost(newPost.getId());
		this.forum.getMessages().flush(this.statisticsThread); //update changes in thread to the persistent
		this.notifyIndexer(member, newPost) ;
	}

	private void notifyIndexer(final User user, final Post newPost)
	{
		if (this.indexAgent != null)
		{
			this.indexAgent.addNewPostToIndex(newPost, ((Member)user).getUsername());
		}
	}

	// TODO
	private boolean statisticsTestDirExists() throws NotFoundException
	{
		Vector<UIDirectory> rootSubdirs = this.getSubDirectories(this.getRootDirectory().getId()) ;
		for (UIDirectory dir: rootSubdirs)
		{
			if (dir.getName().equals("statistics"))
			{
				return true ;
			}
		}
		return false ;
	}

	


}
